/**
 * @file 绘制辅助函数等
 * @author DuskyHuang 圣子
*/

import {
    Color, GeometryRenderer, Node, Mat4, Vec3, Layers,
    ParticleSystem, CurveRange, GradientRange, Prefab, instantiate, Material, renderer, Sprite, Layout, Widget
} from "cc";
import { core, TweenSwitcher } from "cfw";
import { LAYER_UI_2D, LAYER_UI_BG, LBL_GREEN, LBL_BLUE, LBL_ROSE, LBL_YELLOW, Res, StarSpectrum, TransformEnum, LBL_NAVY, interstellar, TRANS_INTER } from "const";
import { CelestialLabel, Dsm, ICelestialSpecific, ITransformInfo, Planet, Star, Blackhole } from "prefabs";
import { UIProxy } from "../proxies/ui-proxy";
import { GizmosProxy } from "../proxies/gizmos-proxy";
import { visit } from "../proxies/visit-proxy";
// import "./cc";
/**
 * 绘制天球网格
 * @param gr 几何渲染器
 * @param radius 天球半径
 * @param hor 横向网格细分度
 * @param ver 纵向网格细分度
 * @param color 网格颜色
 * @param inclination 天球轴倾向角（沿Z轴）
 * @deprecated 该方法增加CPU的负担，现在天球网格直接使用shader绘制
*/
export function drawSphereGrids(gr: GeometryRenderer, radius: number, hor: num.deg, ver: num.deg, color: Color, inclination?: num.deg): void {
    const col = Math.floor(360 / hor), row = Math.floor(360 / ver);
    const inc = isNum(inclination) ? Math.parseRadian(inclination) : 0;
    for (let c = 0; c < col; c++) {
        gr.addCircle(Vec3.ZERO, radius, color, 128, false, true,
            Mat4.fromXRotation(mat4, Math.PI / 2 + inc).rotate(Math.PI / col * c, Vec3.UNIT_Z));
    }
    for (let r = 1; r < row; r++) {
        const rad = r * Math.PI / row - Math.PI / 2;
        gr.addCircle(Vec3.ZERO, Math.cos(rad) * radius, color, 128, false, true,
            Mat4.fromTranslation(mat4, v3a.set(0, Math.sin(rad) * radius, 0)).rotate(inc, Vec3.UNIT_Z));
    }
}


export function addLayer(node: Node, layer: string | number): Node {
    node.layer &= 1 << (isNum(layer) ? layer : Layers.nameToLayer(layer));
    return node;
}

export function setLayer(node: Node, layer: string | number): Node {
    node.layer = 1 << (isNum(layer) ? layer : Layers.nameToLayer(layer));
    return node;
}

export function setLabelStyle(label: CelestialLabel, style: 'stars', north: boolean): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'const', main: boolean): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'galaxy', sub: 'center' | 'companion' | 'dwarf', north: boolean): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'milky', main: boolean): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'struct', sub: substructures): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'cluster' | 'nebula', north: boolean): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'ruler', type?: 'polar' | 'equator' | 'unit' | 'title'): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'solar', type: 'sun' | 'planet' | 'dwarf' | 'minor' | 'belts' | 'comet' | 'star' | 'blackhole'): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: 'planetary', type: 'planet' | 'satellite' | 'axis' | 'diameter'): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: Exclude<style, 'stars' | 'const' | 'cluster' | 'nebula' | 'milky' | 'galaxy' | 'ruler' | 'struct' | 'solar' | 'planetary'>): CelestialLabel;
export function setLabelStyle(label: CelestialLabel, style: style, add1?: boolean | string, add2?: boolean): CelestialLabel {
    label.alwaysInCanvas = false;
    label.locate = '中心';
    label.scalable = false;
    label.color = LabelColor.DEFAULT;
    label.fontSize = FontSize.DEFAULT;
    setLayer(label.node, LAYER_UI_2D);
    label.bold = label.italic = false;
    label.char = true;
    label.perspective = false;
    label.setUIOffset(0, 0);
    label.setOffset(0, 0, 0);
    label.node.setScale(Vec3.ONE);
    switch (style) {
        case 'stars':
        case 'cluster':
        case 'nebula': {
            label.color = LabelColor[style.toUpperCase()];
            label.fontSize = FontSize[style.toUpperCase()];
            label.locate = add1 ? '上标' : '下标';
            label.setOffset(0, add1 ? 0.6 : -0.6, 0);
            label.scalable = true;
            setLayer(label.node, LAYER_UI_BG);
        } break;
        case 'planetary':
        case 'solar': {
            const type = add1 as string;
            label.color = LabelColor[type.toUpperCase()];
            label.fontSize = FontSize.DEFAULT;
            if (['sun', 'planet', 'satellite'].contains(type)) {
                label.alwaysInCanvas = true;
            }
            if (type !== 'belts') {
                label.setOffset(0, 0.55, 0);
            }
            if (['sun', 'star', 'planet'].excludes(type)) {
                label.fontSize = FontSize.SECONDARY;
            }
            label.locate = (add1 === 'comet' ? '上右' : '上标');
            label.scalable = true;
        } break;
        case 'const': {
            label.color = LabelColor.CONST;
            label.fontSize = add1 ? FontSize.CONST : FontSize.CONST_SML;
            setLayer(label.node, LAYER_UI_BG);
        } break;
        case 'milky': {
            label.color = LabelColor.MILKY;
            label.fontSize = add1 ? FontSize.MAIN : FontSize.DEFAULT;
            label.scalable = true;
            label.perspective = true;
        } break;
        case 'struct': {
            switch (add1) {
                // case 'arms': label.color = LineColor.Observable; break; // TODO
                case 'subgroups': label.color = LineColor.LocalGG; break;
                case 'groups': label.color = LineColor.VirgoSC; break;
                case 'clusters': label.color = LineColor.Laniakea; label.fontSize = FontSize.MAIN; break;
                case 'great-walls': label.color = LineColor.Observable; label.fontSize = FontSize.MAIN; break;
            }
            label.fontSize = add1 === 'helios' ? FontSize.DEFAULT : FontSize.MAIN;
            label.scalable = label.perspective = true;
        } break;
        case 'coord': {
            label.fontSize = FontSize.COORD;
            setLayer(label.node, LAYER_UI_BG);
        } break;
        case 'galaxy': {
            label.color = (add1 === 'companion') ? LabelColor.COMPANION : LabelColor.GALAXY;
            label.fontSize = (add1 === 'center') ? FontSize.GALAXY : FontSize.DWARF;
            label.locate = add2 ? '上标' : '下标';
            label.setOffset(0, add2 ? 0.7 : -0.7, 0);
            label.scalable = true;
        } break;
        case 'ruler': {
            switch (add1) {
                case 'equator': {
                    label.fontSize = FontSize.EQUATOR;
                    label.bold = true;
                    setLayer(label.node, LAYER_UI_BG);
                } break;
                case 'polar': {
                    label.fontSize = FontSize.POLAR;
                    label.bold = true;
                    setLayer(label.node, LAYER_UI_BG);
                } break;
                case 'unit': {
                    label.fontSize = FontSize.RULER;
                    label.color = LineColor.LIGHT_YEAR;
                } break;
                case 'title': {
                    label.fontSize = FontSize.TITLE;
                    label.scalable = label.perspective = true;
                } break;
                default: {
                    label.fontSize = FontSize.DEFAULT;
                    label.scalable = label.perspective = true;
                } break;
            }
        } break;
    }
    return label;
}

/** @deprecated */
export function loadUnitAndColor(label: CelestialLabel, bit: num.int, solar: boolean, planetary: boolean): CelestialLabel {
    let str = '';
    if (bit >= 0) {
        if (bit >= 12) str = Math.pow(10, bit - 12) + '万亿光年';
        else if (bit >= 8) str = Math.pow(10, bit - 8) + '亿光年';
        else if (bit >= 4) str = Math.pow(10, bit - 4) + '万光年';
        else str = Math.pow(10, bit) + '光年';
    } else if (bit >= -5) {
        bit += 5;
        if (bit >= 4) str = Math.pow(10, bit - 4) + '万天文单位';
        else str = Math.pow(10, bit) + '天文单位';
    } else {
        bit += 13;
        if (bit >= 4) str = Math.pow(10, bit - 4) + '万公里';
        else str = Math.pow(10, bit) + '公里';
    }
    label.string = str;
    label.color = str.endsWith('光年') ? LBL_YELLOW : solar ? LBL_ROSE : planetary ? LBL_GREEN : LBL_BLUE;
    return label;
}

// 单位显示
export function loadUnitAndColorI18n(label: CelestialLabel, bit: num.int, solar: boolean, planetary: boolean): CelestialLabel {
    label.key = 'units@b' + bit;
    label.color = bit >= 0 ? LBL_YELLOW : solar ? LBL_ROSE : planetary ? LBL_NAVY : LBL_BLUE;
    return label;
}

/**@deprecated 使用同一颜色否则眼花缭乱 */
// 连线颜色
export function getIndicateLine(color: Color, important: boolean, mag: magnitude): Color {
    color.set(LineColor.STARS);
    if (important) return color;
    const dec = Math.clamp(0, 4, Math.floor(mag - 1));
    color.g -= dec * 48;
    color.r += dec * 48;
    color.a -= dec * 16;
    return color;
}

/**
 * 恒星光谱类型转星表颜色
 * @explain 参见赫罗图
*/
export function setColorBySpectrum(out: Color, arg: StarSpectrum | string, name?: string): StarSpectrum {
    const res = out ?? new Color;
    const spectrum = isStr(arg) ? StarSpectrum[arg.charAt(0).toUpperCase()] : arg;
    if (!isNum(spectrum)) {
        console.warn('undefined spectrum:', arg, name);
        res.set(Color.WHITE);
        return A;
    }
    spectrum2color(res, spectrum);
    return spectrum;
}

export function spectrum2color(out: Color, spectrum: StarSpectrum): Color {
    const res = out ?? new Color;
    switch (spectrum) {
        case O: res.set(63, 127, 255, 255); break;      // 蓝
        case B: res.set(127, 191, 255, 255); break;     // 蓝白
        case A: res.set(191, 233, 255, 255); break;     // 白偏蓝
        case F: res.set(255, 255, 223, 255); break;     // 白偏黄
        case G: res.set(255, 239, 127, 255); break;     // 橙黄
        case K: res.set(255, 191, 111, 255); break;     // 橙
        case M: res.set(255, 127, 111, 255); break;     // 红
        case R: res.set(255, 127, 47, 255); break;
        case C: case S:
            res.set(255, 47, 47, 255); break;
    }
    return res;
}

/** @deprecated尺度说明文字 */
export const ScopeTips: Partial<Record<scalar, [title: string, desc: string]>> = {
    Solar: ['狭义太阳系（辐射范围）', '（直径≈79天文单位或118亿公里）'],
    Helio: ['广义太阳系（引力极限）', '（直径≈10万天文单位或15万亿公里）'],
    MilkyWay: ['银河系', '（直径10万光年）'],
    LocalGG: ['本星系群', '（直径1000万光年）'],
    VirgoSC: ['室女座超星系团', '（本星系团，直径1.1亿光年）'],
    Laniakea: ['拉尼亚凯亚超星系团', '（超星系团，直径5.2亿光年）'],
    Observable: ['可观测宇宙', '（哈勃直径：930亿光年）'],
}

/**
 * 新建指定名称/父节点和层的新节点
*/
export function createNodeAt(name: string, layer: number, at?: Node, template?: Prefab): Node {
    const created = template ? instantiate(template) : new Node();
    created.name = name;
    if (at) created.parent = at;
    return setLayer(created, layer);
}

/**
 * 粒子参数化
*/
export function setParticles(target: Node, args: IParticleLike, container?: Node): Node {
    const { num, sizes, colors, color, radius, thickness, random, position, scale, euler } = args;
    const particles = target.getComponent(ParticleSystem)!;
    particles.capacity = particles.rateOverTime.constant = num ?? 1000;
    /** @notice 部分移动平台此值过大会导致粒子无法显示 */
    // particles.startLifetime.constant = 99999;
    if (notEmpty(sizes)) {
        particles.startSize3D = false;
        particles.startSizeX.mode = CurveRange.Mode.TwoConstants;
        const [min, max] = sizes;
        particles.startSizeX.constantMin = min;
        particles.startSizeX.constantMax = max;
    }
    if (notEmpty(colors)) {
        particles.startColor.mode = GradientRange.Mode.TwoColors;
        const [from, to] = colors;
        particles.startColor.colorMin = from as Color;
        particles.startColor.colorMax = to as Color;
    } else {
        particles.startColor.mode = GradientRange.Mode.Color;
        particles.startColor.color = (color ?? Color.WHITE) as Color;
    }
    const shape = particles.shapeModule;
    shape.radius = radius ?? 1;
    shape.radiusThickness = thickness ?? 0.5;
    shape.randomPositionAmount = random ?? 0;
    shape.position.set(Vec3.copy(v3a, position ?? Vec3.ZERO));
    shape.scale.set(Vec3.copy(v3a, scale ?? Vec3.ONE));
    shape.rotation.set(Vec3.copy(v3a, euler ?? Vec3.ZERO));
    if (container) target.parent = container;
    if (!particles.isPlaying) particles.play();
    return target;
}

/**
 * 模型参数化
*/
export function setClouds(target: Node, args: ICloudLike, container?: Node): Node {
    const { position, scale, euler, worldPosition } = args as ICloudLike;
    target.setPosition(Vec3.copy(v3a, position ?? Vec3.ZERO));
    target.setScale(Vec3.copy(v3a, scale ?? Vec3.ONE));
    target.setRotationFromEuler(Vec3.copy(v3a, euler ?? Vec3.ZERO));
    if (container) target.parent = container;
    if (worldPosition) target.setWorldPosition(Vec3.copy(v3a, worldPosition));
    return target;
}

export function preloadCelTexture(name: sun | planet, key: matKey, duplex: boolean = false, type: str.ext = 'jpg'): void {
    const low = name.toLowerCase();
    const base = UIProxy.use4KTex ? Res.Textures.CELESTIAL_4K : Res.Textures.CELESTIAL;
    const prefix = `${base}/${low}-${key}`;
    if (duplex) {
        core.res.loadRemoteTexture(prefix + '-0', type);
        core.res.loadRemoteTexture(prefix + '-1', type);
    } else {
        core.res.loadRemoteTexture(prefix, type);
    }
}

export async function loadCelTexture(mat: Material | renderer.MaterialInstance,
    name: sun | planet | `dwarfs-${number}-high`, prop: string, key: matKey,
    duplex: boolean = false, type: str.ext = 'jpg'): Promise<void> {
    const low = name.toLowerCase();
    const base = UIProxy.use4KTex ? Res.Textures.CELESTIAL_4K : Res.Textures.CELESTIAL;
    const prefix = `${base}/${low}-${key}`;
    if (duplex) {
        mat.setProperty(prop + '0', await core.res.loadRemoteTexture(prefix + '-0', type));
        mat.setProperty(prop + '1', await core.res.loadRemoteTexture(prefix + '-1', type));
    } else {
        mat.setProperty(prop, await core.res.loadRemoteTexture(prefix, type));
    }
}

export function setMatScaleVec3(mat: Material | renderer.MaterialInstance, prop: string, num: num.ratio): void {
    mat.setProperty(prop, v3a.set(num, num, num));
}

export function setMatColor(mat: Material | renderer.MaterialInstance, prop: string, color: rgb16, alpha: num.pct = 1): void {
    Color.fromHEX(col, color ?? white);
    col.a = (alpha ?? 1) * 255;
    mat.setProperty(prop, col);
}

// 数字文字每三位加逗号
export function toThousands(nstr: string): string {
    let res = '';
    while (nstr.length > 3) {
        res = ',' + nstr.slice(-3) + res;
        nstr = nstr.slice(0, nstr.length - 3);
    }
    if (nstr) { res = nstr + res; }
    return res;
}

export function loadTransformType(info: Partial<ITransformInfo>): void {
    const { into, from: { node: { name: fromName }, cat }, name: intoName } = info;
    const toStellar = interstellar.contains(into) && intoName != 'Sun';
    const fromStellar = interstellar.contains(cat as into) && fromName != 'Sun';
    if (toStellar || fromStellar) {
        info.transEnum = toStellar ? (fromStellar ? TransformEnum.SPC_2_SPC : TransformEnum.SUN_2_SPC) : TransformEnum.SPC_2_SUN;
        // const lastRefer = (info.from as ICelestialSpecific).refer ?? null;
        info.stellar = {
            cat: into, name: fromName as celestial, farthest: (info.from as ICelestialSpecific).farthest,
            refers: { from: null, to: null, temp: null }
        };
        return;
    }
    if (fromName === 'Sun') {
        info.transEnum = into === 'satellite' ?
            TransformEnum.SUN_2_SAT : TransformEnum.SUN_2_PLN;
    } else if (cat === 'satellite') {
        if (info.name === 'Sun') info.transEnum = TransformEnum.SAT_2_SUN;
        else if (into === 'satellite') info.transEnum = TransformEnum.SAT_2_SAT;
        else info.transEnum = TransformEnum.SAT_2_PLN;
    } else {
        if (info.name === 'Sun') info.transEnum = TransformEnum.PLN_2_SUN;
        else if (into === 'satellite') info.transEnum = TransformEnum.PLN_2_SAT;
        else info.transEnum = TransformEnum.PLN_2_PLN;
    }
}

export function calcTransformParams(info: Partial<ITransformInfo>, sunRefer: Node): [birdView: pow10, azimuthAngle: num.deg, sign: 1 | -1] {
    const { transEnum, target, mediator, from, stellar } = info;
    const to = mediator ?? target;
    Vec3.subtract(v3a, from.node.worldPosition, to.node.worldPosition);
    const { x, y, z } = v3a.normalize();
    const az = Math.parseDegree(Math.atan2(x, z));
    const sign = y >= -0.1 ? 1 : -1;
    if (TRANS_INTER.contains(transEnum)) {
        switch (transEnum) {
            case TransformEnum.SUN_2_SPC: {
                stellar.refers.from = sunRefer;
                stellar.refers.to = (to as ICelestialSpecific)?.refer;
                return [(to as Star).farthest, az, sign];
            }
            case TransformEnum.SPC_2_SUN: {
                stellar.refers.from = (from as ICelestialSpecific)?.refer;
                stellar.refers.to = sunRefer;
                return [(from as Star).farthest, az, sign];
            }
            case TransformEnum.SPC_2_SPC: {
                stellar.refers.from = (info.from as ICelestialSpecific).refer;
                stellar.refers.to = stellar.refers.temp;
                // try this will be better?
                // Vec3.subtract(v3a, stellar.refers.from.worldPosition, stellar.refers.to.worldPosition);
                // const { x, y, z } = v3a.normalize();
                // const az = Math.parseDegree(Math.atan2(x, z));
                // const sign = y >= -0.1 ? 1 : -1;
                // end try
                return [Math.max((to as Star).farthest, stellar.farthest), az, sign];
            }
            default: console.error("unhandled transEnum", transEnum);
        }
    } else {
        switch (transEnum) {
            case TransformEnum.SUN_2_PLN: return [(to as Planet).panorama, az, sign];
            case TransformEnum.SUN_2_SAT: return [(to.belong as Planet).panorama, az, sign];
            case TransformEnum.PLN_2_SUN: return [(from as Planet).panorama + LN1_3, az, sign];
            case TransformEnum.SAT_2_SUN: return [(from.belong as Planet).panorama + LN1_3, az, sign];
            case TransformEnum.PLN_2_SAT: return [(to as Dsm).panorama, az, sign];
            case TransformEnum.SAT_2_PLN: return [(from as Dsm).panorama + LN1_3, az, sign];
            case TransformEnum.SAT_2_SAT: return [Math.max(from.panorama, to.panorama), az, sign];
            case TransformEnum.PLN_2_PLN: return [Math.max(from.panorama, to.panorama), az, sign];
            default: console.error("unhandled transEnum", transEnum);
        }
    }
}

export function changeFrame(sprite: Sprite, sheet: string, key: 'en' | 'chk' | 'dis', use: boolean, separator: string = '-'): Sprite {
    const tag = separator + key;
    const name = sprite.spriteFrame.name.replace(tag, '') + (use ? tag : '');
    if (name === sprite.spriteFrame.name) return sprite;
    sprite.spriteFrame = core.res.getSheetFrame(sheet, name);
    return sprite;
}

export function updateSwitcherLayout(node: Node): void {
    node.getComponent(Layout)?.updateLayout();
    node.getComponent(Widget)?.updateAlignment();
    node.getComponent(TweenSwitcher)?.resetOriginPosition();
}

export function getI18nConfig(key: i18nKey): i18nConfig {
    const lower = key.toLowerCase();
    if (lower.includes('@')) {
        const [about, item] = lower.split('@');
        const configs = core.res.getConfigItem<i18nConfig>(Res.Configs.I18N, about);
        return (configs ?? {})[item];
    } else {
        return core.res.getConfigItem<i18nConfig>(Res.Configs.I18N, lower);
    }
}

export function getPresetPlanetMatInst(name: planet): string {
    switch (name) {
        case 'Mercury': return PlanetPresets.TERRESTRIAL;
        case 'Venus': return PlanetPresets.TERRESTRIAL;
        case 'Earth': return PlanetPresets.EARTH;
        case 'Mars': return PlanetPresets.TERRESTRIAL;
        case 'Jupiter': return PlanetPresets.WINDY;
        case 'Saturn': return PlanetPresets.GIANT;
        case 'Neptune': return PlanetPresets.WINDY;
        case 'Uranus': return PlanetPresets.GIANT;
    }
}

export function setStarAppearance(star: Star): void {
    const { color, noisy, halo, prom, corona, disk, diskAtt } = star.config;
    const [_main, _spots, _rim] = color.split(',');
    const [_strong, _density, _spot, _speed] = noisy.split(',');
    const [_halo, _hScale, _hDensity, _hSpeed] = halo.split(',');
    const [_prom, _pScale] = prom.split(',');
    const [_corona, _cScale] = corona.split(',');
    star.setSurfaceColors(Color.fromHEX(Main, _main ?? white), Color.fromHEX(Spots, _spots ?? white), Color.fromHEX(Rim, _rim ?? white));
    star.setSurfaceNoisySpeeds(+_strong, +_density, +_spot, +_speed);
    star.setHalo(Color.fromHEX(Halo, _halo ?? white), +_hScale, +_hDensity, +_hSpeed);
    star.setProminence(Color.fromHEX(Prom, _prom ?? white), +_pScale);
    star.setCoronas(Color.fromHEX(Corona, _corona ?? white), +_cScale);
    if (!!disk) {
        const [_disk, _dScale, _dAlpha] = disk.split(',');
        Color.fromHEX(Start, _disk ?? white);
        if (isNum(+_dAlpha)) Start.a = +_dAlpha * 255;
        star.useDiskParticles(true, Start, +_dScale, diskAtt);
    } else star.useDiskParticles(false);
}

export function setBlackholeAppearance(bkh: Blackhole): void {
    const { disk: _disk, jet: _jet, star: _star, vortex: _vtx } = bkh.config;
    const { jets, star, vortex } = bkh;
    const { colors, att, bri, cst, dens, flow } = _disk;
    const [dcs, dcf] = colors ?? [white, white];
    const [ai0, ai1, ao1, ao0] = att ?? [0, 0.15, 0.5, 1];
    const [dT, dR] = dens ?? [1, 1];
    const [fT, fR] = flow ?? [4, 10];
    bkh.diskColorStart = Color.fromHEX(Start, dcs ?? white);
    bkh.diskColorFade = Color.fromHEX(Fade, dcf ?? white);
    bkh.diskAttenuation.set(ai0, ai1, ao1, ao0);
    bkh.diskBrightness = Math.clamp(bri, 0, 3);
    bkh.diskContrast = Math.clamp01(cst);
    bkh.diskDensity.set(dT, dR);
    bkh.diskFlowSpeed.set(fT, fR);
    bkh.updateDisks();
    if (_jet) {
        jets.active = true;
        const { colors, flow, scale } = _jet;
        const [jcs, jcf] = colors ?? [white, white];
        const [fjT, fjR] = flow ?? [2, 5];
        const [sjx, sjy] = scale ?? [1, 1];
        bkh.jetColorStart = Color.fromHEX(Start, jcs ?? white);
        bkh.jetColorFade = Color.fromHEX(Fade, jcf ?? white);
        bkh.jetCylinderScale.set(sjx, sjy);
        bkh.jetFlowSpeed.set(fjT, fjR);
    } else jets.active = false;
    bkh.updateJets();
    if (_star) {
        star.node.active = true;
        const { color, noisy, halo, prom, corona, diam } = _star;
        const [_main, _spots, _rim] = color.split(',');
        const [_strong, _density, _spot, _speed] = noisy.split(',');
        const [_halo, _hScale, _hDensity, _hSpeed] = halo.split(',');
        const [_prom, _pScale] = prom.split(',');
        const [_corona, _cScale] = corona.split(',');
        star.setSurfaceColors(Color.fromHEX(Main, _main ?? white), Color.fromHEX(Spots, _spots ?? white), Color.fromHEX(Rim, _rim ?? white));
        star.setSurfaceNoisySpeeds(+_strong, +_density, +_spot, +_speed);
        star.setHalo(Color.fromHEX(Halo, _halo ?? white), +_hScale, +_hDensity, +_hSpeed);
        star.setProminence(Color.fromHEX(Prom, _prom ?? white), +_pScale);
        star.setCoronas(Color.fromHEX(Corona, _corona ?? white), +_cScale);
        star.loadAsCompanion(bkh.viewer, diam);
        star.node.setPosition(0, 0, 0.85 * diam);
    } else star.node.active = false;
    if (_vtx) {
        vortex.active = true;
        const { colors, att, bri, cst, speed } = _vtx;
        const [vcs, vcf] = colors ?? [white, white];
        const [bi0, bi1, bo1, bo0] = att ?? [0, 0.15, 0.5, 1];
        bkh.vortexColorStar = Color.fromHEX(Start, vcs ?? white);
        bkh.vortexColorDisk = Color.fromHEX(Fade, vcf ?? white);
        bkh.vortexAttenuation.set(bi0, bi1, bo1, bo0);
        bkh.vortexBrightness = bri;
        bkh.vortexContrast = Math.clamp01(cst);
        bkh.vortexFlowSpeed = speed;
    } else vortex.active = false;
    bkh.updateVortexStar();
}

const { PlanetPresets } = Res.Materials;
const LN1_3 = -0.477;
const { FontSize, LabelColor } = UIProxy;
const { LineColor, } = GizmosProxy;
const { O, B, A, F, G, K, M, C, S, R } = StarSpectrum;
const col = new Color;
const v3a = new Vec3;
const mat4 = new Mat4;
const white: rgb16 = "#FFFFFF";

const Main = new Color;
const Halo = new Color;
const Prom = new Color;
const Corona = new Color;
const Spots = new Color;
const Rim = new Color;
const Start = new Color;
const Fade = new Color;

const _FF = 255;
const _7F = 127;
const _DF = 223;
const _3F = 63;
const BLK = Color.BLACK.clone();
const WHT = Color.WHITE.clone();
const RED = Color.RED.clone();
const YLW = Color.YELLOW.clone();
const GRY = Color.GRAY.clone();
const ORG = new Color(_FF, _7F, 0, _FF);
const RED_ORG = new Color(_FF, _3F, 0, _FF);
const LIT_YLW = new Color(_FF, _FF, _7F, _FF);